package scuts.profiler;
import haxe.rtti.Meta;
import haxe.Timer;
import scuts.Core;
import scuts.format.FloatFormat;
import scuts.format.StringFormat;
using scuts.Core;
/**
 * ...
 * @author 
 */

typedef Stats = {
	totalTime:Float,
	totalCalls:Int,
	runTime:Float,
	details: Array<{fn:String, calls:Int, time:Float, percentage:Float}>
}
 
class Profiler 
{
  static var initialized:Bool = false;
  
  
	static var calls:Hash<{count:Int, time:Float}>;
	
	static var stack:List<{id:String, lastStamp:Float, ref:{count:Int, time:Float}}>;
	
  
  public static function init () {
    initialized = true;
    stack = new List();
    calls = {
		
		var h = new Hash<{count:Int, time:Float}>();
		
		var meta = Meta.getType(Profiler);
		var all:Array<String> = meta.fn[0];
		
		for (a in all) {
			h.set(a, { count:0, time:0.0 } );
		}
		
		h;
	}
  }
  
	
	static var running:Bool = true;
	static var profilerStart:Float = 0;
	public static function enterFunction (id:String) {
		
    if (!initialized) return;
    var stamp = Timer.stamp();
		
		if (!stack.isEmpty()) {
			var f = stack.first();
			f.ref.time += stamp - f.lastStamp;
			f.lastStamp = stamp;
		} else {
			profilerStart = Timer.stamp();
		}
		
		var ref = calls.get(id);
		ref.count++;
		stack.push( { id:id, lastStamp:Timer.stamp(), ref:ref } );
	
	}
	
	public static function exitFunction () {

    if (!initialized) return;
    
		var stamp = Timer.stamp();
    if (!stack.isEmpty()) {
      var f = stack.first();
      f.ref.time += stamp - f.lastStamp;
      stack.pop();
    }
    
		if (!stack.isEmpty()) {
			var f = stack.first();
			f.lastStamp = Timer.stamp();
		}

	}
	
	public static function buildStats (listOnlyCalledFunctions:Bool = true):Stats {
		var data = [];
		var totalTime = 0.0;
		var totalCalls = 0;
		
		var calls = if (listOnlyCalledFunctions) {
			var h = new Hash();
			for (k in calls.keys()) {
				var e = calls.get(k);
				if (e.count > 0) {
					h.set(k, e);
				}
			}
			h;
		} else {
			calls;
		}
		
		for (c in calls) {
			totalTime += c.time;
			totalCalls += c.count;
		}
		
		for (key in calls.keys()) {
			var entry = calls.get(key);
			data.push( { fn: key, calls: entry.count, time: entry.time, percentage: 100/totalTime * entry.time} );
			
		}
		// order
		data.sort(function (e1, e2) {
			return 
				if (e1.time > e2.time) -1
				else if (e1.time < e2.time) 1
				else 0;
		});
		return { runTime: Timer.stamp() - profilerStart, totalTime: totalTime, totalCalls:totalCalls, details:data};
	}
	
	public static function traceStats (?limit:Int):Void {
    
    var floatF = FloatFormat.toPrecision.curryR(5);
    
    var limit = limit.toOption();
		var t = Timer.stamp();
		var data = buildStats();
		t = Timer.stamp() -t;
		var s = "\n\nProfiler Stats\n\n";
		s += "Totalcalls: " + data.totalCalls + "\n";
		s += "Totaltime: " + floatF(data.totalTime) + "\n\n";
		s += "App Runtime with Profiler: " + floatF(data.runTime) + "\n\n";
		s += "Build Stats Time: " + t + "\n\n";
		
		
    var det = 
      if (limit.isSome()) {
        var det = [];
        var lim = limit.value();
        for (d in data.details) {
          if (lim-- <= 0) break;
          det.push(d);
            
        }
        det;
      }
      else 
        data.details;
    
    var longestStringLength = det.selectF(function (a, b) return a.fn.length > b.fn.length ? a : b).fn.length;
    
    var stringF = StringFormat.fillLeft.flip().curry(longestStringLength);
    
    
		for (d in det) {
			s += stringF(d.fn, " ") + ": " + floatF(d.percentage) + "% - total time: " + floatF(d.time) + " (calls:" + d.calls + ", time per call:" + floatF(d.time/d.calls) + ")\n";
		}
		s += "\n\n";
		trace(s);
	}
	
	
	
	
}